package com.xiaoxin.experience.util;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

public class JSONUtil
{
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private static final ObjectMapper LOG_OBJ_MAPPER = new ObjectMapper();

    /**
     * 获取日志记录器
     */
    private static final Logger LOG = LoggerFactory.getLogger(JSONUtil.class);

    static
    {
        //通用配置
        genericConfig(OBJECT_MAPPER);
        registerJavaTime(OBJECT_MAPPER);

        LOG_OBJ_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        LOG_OBJ_MAPPER.configure(JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS.mappedFeature(), true);
        LOG_OBJ_MAPPER.setDateFormat(new SimpleDateFormat(DateUtil.YMDHMS_STD));
        registerJavaTime(LOG_OBJ_MAPPER);
    }

    public static void genericConfig(ObjectMapper om)
    {
        om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        om.configure(JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS.mappedFeature(), true);
        om.configure(Feature.ALLOW_COMMENTS, true);
        om.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        om.configure(Feature.ALLOW_SINGLE_QUOTES, true);
        om.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
        om.setDateFormat(new SimpleDateFormat(DateUtil.YMDHMS_STD));
        om.setTimeZone(TimeZone.getTimeZone("GMT+8"));
    }

    /**
     * 支持LocalDateTime、LocalDate、LocalTime序列化和反序列化
     */
    public static void registerJavaTime(ObjectMapper om)
    {
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DateUtil.YMDHMS_STD);
        DateTimeFormatter localTimeFormatter = DateTimeFormatter.ofPattern(DateUtil.HMS_STD);
        DateTimeFormatter localDateFormatter = DateTimeFormatter.ofPattern(DateUtil.YMD_DASH);
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(localTimeFormatter));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(localDateFormatter));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(localTimeFormatter));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(localDateFormatter));
        om.registerModule(javaTimeModule);
    }

    public static void addModule(Module module)
    {
        LOG_OBJ_MAPPER.registerModule(module);
        OBJECT_MAPPER.registerModule(module);
    }

    public static void addLogModule(Module module)
    {
        LOG_OBJ_MAPPER.registerModule(module);
    }

    public static void setSerializationInclusion(JsonInclude.Include include)
    {
        OBJECT_MAPPER.setSerializationInclusion(include);
    }

    /**
     * <将Object对象（如pojo类,List列表,Map等）转换成JSON结构字符串>
     *
     * @return JSON结构字符串，转换失败时返回空字符串
     */
    public static String toJson(Object obj)
    {
        return toJson(obj, false);
    }

    public static String toJson(Object obj, boolean pretty)
    {
        try
        {
            if (pretty)
            {
                return OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
            }
            else
            {
                return OBJECT_MAPPER.writeValueAsString(obj);
            }
        }
        catch (Exception e)
        {
            LOG.error("toJson fail: ", e);
            return "";
        }
    }

    public static String toJson(ObjectMapper ob, Object obj, boolean pretty)
    {
        try
        {
            if (pretty)
            {
                return ob.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
            }
            else
            {
                return ob.writeValueAsString(obj);
            }
        }
        catch (Exception e)
        {
            LOG.error("toJson fail: ", e);
            return "";
        }
    }

    /**
     * <JSON结构字符串转换成Object>
     *
     * @return Object对象，转换失败时返回空字符串
     */
    public static <T> T fromJson(String jsonStr, Class<T> clz)
    {
        try
        {
            return OBJECT_MAPPER.readValue(jsonStr, clz);
        }
        catch (Exception e)
        {
            LOG.error("fromJson fail: ", e);
            return null;
        }
    }

    public static Map<String, String> jsonToMap(String jsonStr)
    {
        try
        {
            JavaType jvt =
                OBJECT_MAPPER.getTypeFactory().constructParametricType(HashMap.class, String.class, String.class);
            return OBJECT_MAPPER.readValue(jsonStr, jvt);
        }
        catch (JsonParseException | JsonMappingException je)
        {
            throw new IllegalArgumentException("json string parse fail.", je);
        }
        catch (Exception e)
        {
            LOG.error("jsonToMap fail: ", e);
            return new HashMap<>(0);
        }
    }

    /**
     * <JSON结构字符串转换成Object>
     *
     * @return Object对象，转换失败时返回空字符串
     */
    public static <T> List<T> jsonToList(String jsonStr, Class<T> clz)
    {
        try
        {
            JavaType jt = OBJECT_MAPPER.getTypeFactory().constructParametricType(ArrayList.class, clz);
            return OBJECT_MAPPER.readValue(jsonStr, jt);
        }
        catch (JsonParseException | JsonMappingException je)
        {
            throw new IllegalArgumentException("json string parse fail.", je);
        }
        catch (Exception e)
        {
            LOG.error("jsonToList fail: ", e);
            return new ArrayList<>(0);
        }
    }

    public static Map<String, String> toFlatMap(String jsonStr)
    {
        try
        {
            return toFlatMap(OBJECT_MAPPER.readTree(jsonStr));
        }
        catch (JsonParseException | JsonMappingException je)
        {
            throw new IllegalArgumentException("json string parse fail.", je);
        }
        catch (Exception e)
        {
            LOG.error("jsonToList fail: ", e);
            return new HashMap<>();
        }
    }

    public static Map<String, String> toFlatMap(JsonNode node)
    {
        Map<String, String> metaMap = new HashMap<>();
        toFlatMap(metaMap, null, node);
        return metaMap;
    }

    public static void toFlatMap(Map<String, String> metaMap, String prefix, JsonNode node)
    {
        Iterator<String> iterator = node.fieldNames();
        while (iterator.hasNext())
        {
            String name = iterator.next();
            JsonNode n = node.get(name);

            String key = StringUtils.isEmpty(prefix) ? name : prefix + "." + name;
            if (n.isObject())
            {
                toFlatMap(metaMap, key, n);
            }
            else if (n.isArray())
            {
                Iterator<JsonNode> nIter = n.elements();
                for (int i = 0; nIter.hasNext(); i++)
                {
                    String aKey = i == 0 ? key : key + "#" + i;

                    JsonNode an = nIter.next();
                    if (an.isObject())
                    {
                        toFlatMap(metaMap, aKey, an);
                    }
                    else
                    {
                        metaMap.put(aKey, an.asText());
                    }
                }
            }
            else
            {
                metaMap.put(key, n.asText());
            }
        }
    }

    public static String log(Object obj)
    {
        if (null == obj)
        {
            return "NULL";
        }

        try
        {
            return LOG_OBJ_MAPPER.writeValueAsString(obj);
        }
        catch (Exception e)
        {
            LOG.error("log fail: ", e);
            return "";
        }
    }

    public static String makeLog(Object obj)
    {
        if (null == obj)
        {
            return "NULL";
        }

        String body;
        try
        {
            body = LOG_OBJ_MAPPER.writeValueAsString(obj);
        }
        catch (Exception e)
        {
            LOG.error("makeLog fail: ", e);
            body = "";
        }
        return obj.getClass().getSimpleName() + ": " + body;
    }

    static class PwdFilterSerializer extends JsonSerializer<String>
    {

        @Override
        public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
            throws IOException
        {
            String name = jsonGenerator.getOutputContext().getCurrentName();
            if (null != name && canFilter(name))
            {
                jsonGenerator.writeString("******");
            }
            else
            {
                jsonGenerator.writeString(s);
            }
        }

        private boolean canFilter(String name)
        {
            return "token".equals(name) || "pPWD".equals(name) || "password".equalsIgnoreCase(name) ||
                name.endsWith("Password") || "passwd".equalsIgnoreCase(name) || name.endsWith("Passwd");
        }
    }
}
